# -*- coding: utf-8 -*-
"""
 * @file infer_process.py
 * @author SDK_TEAM
 * @brief
 * @version 0.1
 * @date 2023-8-14
 * Copyright:
 * © 2018 北京灵汐科技有限公司 版权所有。
 * 注意：以下内容均为北京灵汐科技有限公司原创，未经本公司允许，不得转载，否则将视为侵权；对于不遵守此声明或者其他违法使用以下内容者，本公司依法保留追究权。
 * © 2018 Lynxi Technologies Co., Ltd. All rights reserved.
 * NOTICE: All information contained here is, and remains the property of Lynxi. This file can not
 * be copied or distributed without the permission of Lynxi Technologies Co., Ltd.
"""


import json
import math
import queue
import sys
import os

sys.path.append("../")
from .callback_data_struct import *
from .blockqueue import block_queue
from abc import abstractmethod
from typing import List, Tuple
import time
import numpy as np
import pylynchipsdk as sdk
import copy
import common.python.common as common
import common.python.bufferpool as bufferpool
import threading

try:
    import cv2
except ImportError:
    cv2 = None

window_created = False
grid = None
cell_width = None
cell_height = None
total_cells = None

lynEEOF = 100003


class infer_process:
    def __init__(self, attr: infer_process_attr) -> None:
        self.attr = ""
        self.__ctx = ""
        self.__send_thread = ""
        self.__recv_thread = ""
        self.__ipe_thread = ""
        self.__demux_hdl = ""
        self.codec_para = ""
        self.__vdec_hdl = ""
        self.venc_handle = ""
        self.__vdec_attr = sdk.lyn_vdec_attr_t()
        self.__send_queue = block_queue()
        self.__ipe_queue = block_queue()
        # 创建上下文环境
        self.attr = copy.copy(attr)
        self.video_frame = attr.video_frame
        self.__ctx, ret = sdk.lyn_create_context(self.attr.device_id)
        self.enc_head_flag = True
        common.error_check(ret, "create context")
        ret = sdk.lyn_register_error_handler(common.default_stream_error_handle)
        common.error_check(ret, "lyn_register_error_handler")

        if os.path.exists(attr.output_path):
            os.remove(attr.output_path)
        self.use_local_input = os.path.exists(attr.url)
        # 打开解封装器
        self.init_demuxer()

        # 打开解码器
        self.init_decoder()

        common.error_check(ret, "lyn_vdec_get_out_info ")
        # 注册plugin，打开模型
        self.plugin, ret = sdk.lyn_plugin_register(self.attr.plugin_path)
        common.error_check(ret, "lyn_plugin_register")
        self.__model, ret = sdk.lyn_load_model(self.attr.model_path)
        common.error_check(ret, "lyn_load_model")
        # 创建stream、event和ipe desc
        self.send_stream, ret = sdk.lyn_create_stream()
        common.error_check(ret, "lyn_create_stream")
        self.recv_stream, ret = sdk.lyn_create_stream()
        common.error_check(ret, "lyn_create_stream")
        self.ipe_stream, ret = sdk.lyn_create_stream()
        common.error_check(ret, "lyn_create_stream")
        self.apu_stream, ret = sdk.lyn_create_stream()
        common.error_check(ret, "lyn_create_stream")
        self.plugin_stream, ret = sdk.lyn_create_stream()
        common.error_check(ret, "lyn_create_stream")
        self.venc_recv_stream, ret = sdk.lyn_create_stream()
        common.error_check(ret, "lyn_create_stream")
        self.venc_send_stream, ret = sdk.lyn_create_stream()
        common.error_check(ret, "lyn_create_stream")
        self.ipe_event, ret = sdk.lyn_create_event()
        common.error_check(ret, "lyn_create_event")
        self.apu_event, ret = sdk.lyn_create_event()
        common.error_check(ret, "lyn_create_event")
        self.plugin_event, ret = sdk.lyn_create_event()
        common.error_check(ret, "lyn_create_event")
        self.ipe_input_desc, ret = sdk.lyn_ipe_create_pic_desc()
        common.error_check(ret, "lyn_ipe_create_pic_desc ipe_input_desc")
        self.ipe_output_desc, ret = sdk.lyn_ipe_create_pic_desc()
        common.error_check(ret, "lyn_ipe_create_pic_desc ipe_output_desc")
        self.ipe_config, ret = sdk.lyn_ipe_create_config_desc()
        common.error_check(ret, "lyn_ipe_create_config_desc")
        ret = sdk.lyn_ipe_reset_pic_desc(self.ipe_input_desc)
        common.error_check(ret, "lyn_ipe_reset_pic_desc")
        ret = sdk.lyn_ipe_reset_pic_desc(self.ipe_output_desc)
        common.error_check(ret, "lyn_ipe_reset_pic_desc")
        ret = sdk.lyn_ipe_reset_config_desc(self.ipe_config)
        common.error_check(ret, "lyn_ipe_reset_config_desc")
        # 获取模型信息
        self.model_desc, ret = sdk.lyn_model_get_desc(self.__model)
        common.error_check(ret, "lyn_model_get_desc")
        self.batch_size = self.model_desc.inputTensorAttrArray[0].batchSize
        self.model_width = self.model_desc.inputTensorAttrArray[0].dims[2]
        self.model_height = self.model_desc.inputTensorAttrArray[0].dims[1]

        self.ipe_output_size = (
            self.model_width
            * self.model_height
            * self.model_desc.inputTensorAttrArray[0].dims[3]
        )
        self.apu_output_size = self.model_desc.outputDataLen
        (
            self.resize_width,
            self.resize_height,
            self.pad_x,
            self.pad_y,
        ) = self.set_padding_data(
            self.vdec_out_info.width,
            self.vdec_out_info.height,
            self.model_width,
            self.model_height,
        )
        self.attr.width = self.vdec_out_info.width
        self.attr.height = self.vdec_out_info.height

        # 创建对象池
        self.ipe_output_mem_pool = bufferpool.buffer_pool(
            self.ipe_output_size * self.batch_size, 5
        )
        self.apu_output_mem_pool = bufferpool.buffer_pool(
            self.apu_output_size * self.batch_size, 5
        )
        self.venc_recv_pool = bufferpool.buffer_pool(
            self.vdec_out_info.predict_buf_size, 5
        )

        # 设置编码参数
        if self.attr.show_type == 1:
            venc_attr = sdk.lyn_venc_attr_t()
            ret = sdk.lyn_venc_set_default_params(venc_attr)
            common.error_check(ret, "lyn_venc_set_default_params")
            venc_attr.codec_type = sdk.lyn_codec_id_t.LYN_CODEC_ID_H264
            venc_attr.width = self.vdec_out_info.width
            venc_attr.height = self.vdec_out_info.height
            venc_attr.bit_depth = 8
            venc_attr.bframes_num = 0
            venc_attr.pframes_num = 5
            venc_attr.input_format = sdk.lyn_pixel_format_t.LYN_PIX_FMT_NV12
            venc_attr.target_bitrate = 6000000
            venc_attr.level = -1
            self.venc_handle, ret = sdk.lyn_venc_open(venc_attr)
            common.error_check(ret, "lyn_vdec_open")

    def get_ctx(self):
        return self.__ctx

    def get_model(self):
        return self.__model

    def get_vdec_attr(self):
        return self.__vdec_attr

    def init_demuxer(self):
        self.__demux_hdl, ret = sdk.lyn_demux_open(self.attr.url)
        common.error_check(ret, "lyn_demux_open ")
        self.codec_para, ret = sdk.lyn_demux_get_codec_para(self.__demux_hdl)
        common.error_check(ret, "lyn_demux_get_codec_para ")

    def init_decoder(self):
        sdk.lyn_set_current_context(self.__ctx)
        self.__vdec_attr = sdk.lyn_vdec_attr_t()
        self.__vdec_attr.codec_id = self.codec_para.codec_id
        self.__vdec_attr.output_fmt = sdk.lyn_pixel_format_t.LYN_PIX_FMT_NV12
        self.__vdec_attr.scale = sdk.lyn_scale_t.SCALE_NONE
        self.__vdec_hdl, ret = sdk.lyn_vdec_open(self.__vdec_attr)
        common.error_check(ret, "lyn_vdec_open")
        self.vdec_out_info, ret = sdk.lyn_vdec_get_out_info(
            self.codec_para, self.__vdec_attr
        )
        common.error_check(ret, "lyn_vdec_get_out_info")
        self.frame_pool = bufferpool.buffer_pool(self.vdec_out_info.predict_buf_size, 5)
        self.attr.width = self.vdec_out_info.width
        self.attr.height = self.vdec_out_info.height

    def send_thread_func(self, cancel_flag):
        # 设置上下文环境 创建发送stream
        sdk.lyn_set_current_context(self.__ctx)
        eos = False
        while not eos:
            # 从解封装器读取一个包
            pkt, ret = sdk.lyn_demux_read_packet(self.__demux_hdl)
            eos = pkt.eos
            reconnect = ret == 101003
            if reconnect:
                eos = False
                self.__send_queue.put((eos, reconnect))
                ret = sdk.lyn_vdec_send_packet_async(
                    self.send_stream, self.__vdec_hdl, pkt
                )
                self.__ipe_thread.join()
                self.__recv_thread.join()

                self.frame_pool.free_buffers()
                sdk.lyn_vdec_close(self.__vdec_hdl)
                sdk.lyn_demux_close(self.__demux_hdl)

                self.init_demuxer()
                self.init_decoder()
                print(
                    f"cnannel {self.attr.device_id}_{self.attr.chan_id} changed resolution to: {self.vdec_out_info.width}, {self.vdec_out_info.height}"
                )

                self.__recv_thread = threading.Thread(
                    target=self.recv_thread_func, args=(cancel_flag,)
                )
                self.__recv_thread.start()
                self.__ipe_thread = threading.Thread(
                    target=self.ipe_thread_func, args=(cancel_flag,)
                )
                self.__ipe_thread.start()
                continue

            if (eos or ret == lynEEOF) and not self.use_local_input:
                sdk.lyn_demux_close(self.__demux_hdl)
                time.sleep(500.0 / 1000)
                print("demux failed, reconnecting...")
                self.__demux_hdl, ret = sdk.lyn_demux_open(self.attr.url)
                common.error_check(ret, "lyn_demux_open")
                eos = False
                continue

            # 发送给解码器解码
            ret = sdk.lyn_vdec_send_packet_async(self.send_stream, self.__vdec_hdl, pkt)
            common.error_check(ret, "lyn_vdec_send_packet_async")
            ret = sdk.lyn_synchronize_stream(self.send_stream)
            common.error_check(ret, "lyn_synchronize_stream")

            # 释放packet内存并通知接收结果
            if not eos:
                sdk.lyn_demux_free_packet(pkt)
            self.__send_queue.put((eos, reconnect))
            if cancel_flag.value:
                return

    def set_padding_data(self, video_width, video_height, model_width, model_height):
        if video_width > video_height:
            resize_width = model_width
            pad_x = 0
            resize_height = set_even(int(video_height * model_width / video_width))
            pad_y = (model_height - resize_height) / 2
        else:
            resize_height = model_height
            pad_y = 0
            resize_width = set_even(int(video_width * model_width / video_height))
            pad_x = (model_width - resize_width) / 2

        return int(resize_width), int(resize_height), int(pad_x), int(pad_y)

    def recv_thread_func(self, cancel_flag):
        # 设置上下文环境 创建接收stream
        sdk.lyn_set_current_context(self.__ctx)

        eos, reconnect = False, False
        while not eos:
            eos, reconnect = self.__send_queue.take()
            if reconnect:
                cb_data = recv_cb_data()
                cb_data.block_queue = self.__ipe_queue
                cb_data.frame_pool = self.frame_pool
                cb_data.reconnect = reconnect
                ret = sdk.lyn_stream_add_callback(
                    self.recv_stream, recv_frame_cb, cb_data
                )
                self.__send_queue.clear()
                sdk.lyn_synchronize_stream(self.recv_stream)
                break
            cb_data = recv_cb_data()
            cb_data.frame.eos = eos
            cb_data.frame.data = self.frame_pool.pop()
            cb_data.frame.size = self.vdec_out_info.predict_buf_size
            cb_data.frame_pool = self.frame_pool
            cb_data.attr = self.attr
            cb_data.block_queue = self.__ipe_queue
            cb_data.video_frame = self.video_frame
            # 插入接收指令，并添加接收完成回调函数
            ret = sdk.lyn_vdec_recv_frame_async(
                self.recv_stream, self.__vdec_hdl, cb_data.frame
            )
            common.error_check(ret, "lyn_vdec_recv_frame_async")
            ret = sdk.lyn_stream_add_callback(
                self.recv_stream, recv_frame_cb, cb_data
            )
            common.error_check(ret, "lyn_stream_add_callback")
            if cancel_flag.value:
                return

    def ipe_thread_func(self, cancel_flag):
        # 设置上下文环境 创建ipe stream
        sdk.lyn_set_current_context(self.__ctx)
        eos = False
        while not eos:
            cb_data = self.__ipe_queue.take()
            if cb_data.reconnect:
                self.__ipe_queue.clear()
                sdk.lyn_synchronize_stream(self.plugin_stream)
                break

            eos = cb_data.frame.eos
            ipe_out_data = self.ipe_process(cb_data)
            apu_output_data = self.apu_process(ipe_out_data)
            self.plugin_process(apu_output_data, cb_data)
            if self.attr.show_type == 0:
                ret = sdk.lyn_stream_add_callback(
                    self.plugin_stream, show_video_cb, cb_data
                )
                common.error_check(ret, "lyn_stream_add_callback")
            elif self.attr.show_type == 1:
                self.encode_process(cb_data)
            elif self.attr.show_type == 2:
                ret = sdk.lyn_stream_add_callback(
                    self.plugin_stream,
                    free_to_pool_callback,
                    [cb_data.frame_pool, cb_data.frame.data],
                )
                common.error_check(ret, "lyn_stream_add_callback")
            if cancel_flag.value:
                return

    def ipe_process(self, cb_data: framepool_cb_data):
        sdk.lyn_set_current_context(self.__ctx)
        ipe_out_data = self.ipe_output_mem_pool.pop()
        # 设置ipe输入
        ret = sdk.lyn_ipe_set_input_pic_desc(
            self.ipe_input_desc,
            cb_data.frame.data,
            self.vdec_out_info.width,
            self.vdec_out_info.height,
            self.__vdec_attr.output_fmt,
        )
        common.error_check(ret, "lyn_ipe_set_input_pic_desc")
        ret = sdk.lyn_ipe_set_output_pic_data(self.ipe_output_desc, ipe_out_data)
        common.error_check(ret, "lyn_ipe_set_output_pic_data")
        ret = sdk.lyn_ipe_set_resize_config(
            self.ipe_config, self.resize_width, self.resize_height
        )
        common.error_check(ret, "lyn_ipe_set_resize_config")
        ret = sdk.lyn_ipe_set_pad_config(
            self.ipe_config,
            self.pad_y,
            self.pad_x,
            self.pad_y,
            self.pad_x,
            114,
            114,
            114,
        )
        common.error_check(ret, "lyn_ipe_set_pad_config")
        ret = sdk.lyn_ipe_set_c2c_config(
            self.ipe_config, sdk.lyn_pixel_format_t.LYN_PIX_FMT_RGB24, 0
        )
        common.error_check(ret, "lyn_ipe_set_c2c_config")
        ret = sdk.lyn_ipe_cal_output_pic_desc(
            self.ipe_output_desc, self.ipe_input_desc, self.ipe_config, 0
        )
        common.error_check(ret, "lyn_ipe_cal_output_pic_desc")
        ret = sdk.lyn_ipe_process_async(
            self.ipe_stream, self.ipe_input_desc, self.ipe_output_desc, self.ipe_config
        )
        common.error_check(ret, "lyn_ipe_process_async")

        return ipe_out_data

    def get_channel_info(self) -> str:
        return f"{self.attr.device_id}_{self.attr.chan_id}"

    def apu_process(self, ipe_out_data):
        # 等待IPE处理完成
        ret = sdk.lyn_record_event(self.ipe_stream, self.ipe_event)
        common.error_check(ret, "lyn_record_event")
        ret = sdk.lyn_stream_wait_event(self.apu_stream, self.ipe_event)
        common.error_check(ret, "lyn_stream_wait_event")
        apu_output_data = self.apu_output_mem_pool.pop()
        ret = sdk.lyn_execute_model_async(
            self.apu_stream,
            self.__model,
            ipe_out_data,
            apu_output_data,
            self.batch_size,
        )
        common.error_check(ret, "lyn_execute_model_async")
        ret = sdk.lyn_stream_add_callback(
            self.apu_stream,
            free_to_pool_callback,
            [self.ipe_output_mem_pool, ipe_out_data],
        )
        common.error_check(ret, "lyn_stream_add_callback")
        common.print_frame_rate(self.get_channel_info())
        return apu_output_data

    # 由具体的子类完成
    @abstractmethod
    def plugin_process(self, apu_output_data, cb_data):
        pass

    def encode_process(self, cb_data: cb_data):
        # 在 plugin 处理完之后，进行编码操作
        ret = sdk.lyn_record_event(self.plugin_stream, self.plugin_event)
        common.error_check(ret, "lyn_record_event")
        ret = sdk.lyn_stream_wait_event(self.venc_send_stream, self.plugin_event)
        common.error_check(ret, "lyn_stream_wait_event")
        if self.enc_head_flag:  # 第一帧编码
            self.enc_head_flag = False
            enc_packet = sdk.lyn_packet_t()
            enc_packet.size = self.vdec_out_info.predict_buf_size
            enc_packet.data = self.venc_recv_pool.pop()
            encode_data = save_file_cb_data()
            encode_data.output_path = self.attr.output_path
            encode_data.packet = enc_packet
            encode_data.recv_pool = self.venc_recv_pool
            encode_data.file_path = self.attr.output_path
            ret = sdk.lyn_venc_get_paramsset_async(
                self.venc_recv_stream, self.venc_handle, enc_packet
            )
            common.error_check(ret, "lyn_venc_get_paramsset_async")
            ret = sdk.lyn_stream_add_callback(
                self.venc_recv_stream, save_file_cb, encode_data
            )
            common.error_check(ret, "lyn_stream_add_callback")

        ret = sdk.lyn_venc_sendframe_async(
            self.venc_send_stream, self.venc_handle, cb_data.frame
        )
        common.error_check(ret, "lyn_venc_sendframe_async")

        enc_packet = sdk.lyn_packet_t()
        enc_packet.size = self.vdec_out_info.predict_buf_size
        enc_packet.eos = cb_data.frame.eos
        enc_packet.data = self.venc_recv_pool.pop()
        encode_data = save_file_cb_data()
        encode_data.packet = enc_packet
        encode_data.recv_pool = self.venc_recv_pool
        encode_data.file_path = self.attr.output_path
        encode_data.output_path = self.attr.output_path
        ret = sdk.lyn_stream_add_callback(
            self.venc_send_stream,
            free_to_pool_callback,
            [cb_data.frame_pool, cb_data.frame.data],
        )
        common.error_check(ret, "lyn_stream_add_callback")
        ret = sdk.lyn_venc_recvpacket_async(
            self.venc_recv_stream, self.venc_handle, enc_packet
        )
        common.error_check(ret, "lyn_venc_recvpacket_async")
        ret = sdk.lyn_stream_add_callback(
            self.venc_recv_stream, save_file_cb, encode_data
        )
        common.error_check(ret, "lyn_stream_add_callback")

    def run(self, cancel_flag):
        # 开启发送线程
        self.__send_thread = threading.Thread(
            target=self.send_thread_func, args=(cancel_flag,)
        )
        self.__send_thread.start()

        # 开启接收线程
        self.__recv_thread = threading.Thread(
            target=self.recv_thread_func, args=(cancel_flag,)
        )
        self.__recv_thread.start()

        # 开启图像处理和推理线程
        self.__ipe_thread = threading.Thread(
            target=self.ipe_thread_func, args=(cancel_flag,)
        )
        self.__ipe_thread.start()

    def before_ctx_destroy(self):
        pass

    def close(self):
        if self.__send_thread.is_alive():
            self.__send_thread.join()
        if self.__recv_thread.is_alive():
            self.__recv_thread.join()
        if self.__ipe_thread.is_alive():
            self.__ipe_thread.join()

        ret = sdk.lyn_synchronize_stream(self.send_stream)
        common.error_check(ret, "lyn_synchronize_stream")
        ret = sdk.lyn_synchronize_stream(self.recv_stream)
        common.error_check(ret, "lyn_synchronize_stream")
        ret = sdk.lyn_synchronize_stream(self.ipe_stream)
        common.error_check(ret, "lyn_synchronize_stream")
        ret = sdk.lyn_synchronize_stream(self.apu_stream)
        common.error_check(ret, "lyn_synchronize_stream")
        ret = sdk.lyn_synchronize_stream(self.plugin_stream)
        common.error_check(ret, "lyn_synchronize_stream")
        ret = sdk.lyn_synchronize_stream(self.venc_recv_stream)
        common.error_check(ret, "lyn_synchronize_stream")
        ret = sdk.lyn_synchronize_stream(self.venc_send_stream)
        common.error_check(ret, "lyn_synchronize_stream")

        ret = sdk.lyn_destroy_stream(self.send_stream)
        common.error_check(ret, "lyn_destroy_stream")
        ret = sdk.lyn_destroy_stream(self.recv_stream)
        common.error_check(ret, "lyn_destroy_stream")
        ret = sdk.lyn_destroy_stream(self.ipe_stream)
        common.error_check(ret, "lyn_destroy_stream")
        ret = sdk.lyn_destroy_stream(self.apu_stream)
        common.error_check(ret, "lyn_destroy_stream")
        ret = sdk.lyn_destroy_stream(self.plugin_stream)
        common.error_check(ret, "lyn_destroy_stream")
        ret = sdk.lyn_destroy_stream(self.venc_recv_stream)
        common.error_check(ret, "lyn_destroy_stream")
        ret = sdk.lyn_destroy_stream(self.venc_send_stream)
        common.error_check(ret, "lyn_destroy_stream")

        ret = sdk.lyn_destroy_event(self.ipe_event)
        common.error_check(ret, "lyn_destroy_event")
        ret = sdk.lyn_destroy_event(self.apu_event)
        common.error_check(ret, "lyn_destroy_event")
        ret = sdk.lyn_destroy_event(self.plugin_event)
        common.error_check(ret, "lyn_destroy_event")

        # destory ipe desc and config
        ret = sdk.lyn_ipe_destroy_pic_desc(self.ipe_input_desc)
        common.error_check(ret, "lyn_ipe_destroy_pic_desc")
        ret = sdk.lyn_ipe_destroy_pic_desc(self.ipe_output_desc)
        common.error_check(ret, "lyn_ipe_destroy_pic_desc")
        ret = sdk.lyn_ipe_destroy_config_desc(self.ipe_config)
        common.error_check(ret, "lyn_ipe_destroy_config_desc")
        ret = sdk.lyn_plugin_unregister(self.plugin)
        common.error_check(ret, "lyn_plugin_unregister")

        # 卸载模型
        ret = sdk.lyn_unload_model(self.__model)
        common.error_check(ret, "lyn_unload_model")

        if self.__vdec_hdl != "":
            ret = sdk.lyn_vdec_close(self.__vdec_hdl)
            common.error_check(ret, "lyn_vdec_close")
        if self.venc_handle != "":
            ret = sdk.lyn_venc_close(self.venc_handle)

        self.before_ctx_destroy()

        if self.__ctx != "":
            ret = sdk.lyn_destroy_context(self.__ctx)
            common.error_check(ret, "lyn_destroy_context")


def recv_frame_cb(cb_data: recv_cb_data):
    # 放到ipe的处理队列中
    cb_data.block_queue.put(cb_data)
    return 0


def free_to_pool_callback(params):
    vdec_out_pool = params[0]
    data = params[1]
    vdec_out_pool.push(data)
    return 0


def set_even(num):
    num = math.floor(num)
    if num % 2 != 0:
        res = num - 1
    else:
        res = num
    return res


def set_padding_data(video_width, video_height, model_width, model_height):
    if video_width > video_height:
        resize_width = model_width
        pad_x = 0
        resize_height = set_even(int(video_height * model_width / video_width))
        pad_y = (model_height - resize_height) / 2
        if pad_y < 0:
            resize_height = model_height
            resize_width =  set_even(int(model_height * video_width / video_height))
            pad_x = (model_width - resize_width) / 2
            pad_y = 0
            if (pad_x + pad_x + resize_width != model_width):
                pad_x = model_width - (pad_x + pad_x + resize_width)
    else:
        resize_height = model_height
        pad_y = 0
        resize_width = set_even(int(video_width * model_width / video_height))
        pad_x = (model_width - resize_width) / 2

    return int(resize_width), int(resize_height), int(pad_x), int(pad_y)


def show_video_cb(cb_data: cb_data):
    data = np.empty(cb_data.frame.size, dtype=np.uint8)
    data_ptr = sdk.lyn_numpy_to_ptr(data)
    ret = sdk.lyn_memcpy(
        data_ptr,
        cb_data.frame.data,
        cb_data.frame.size,
        sdk.lyn_memcpy_dir_t.ServerToClient,
    )
    common.error_check(ret, "lyn_memcpy")
    yuvImg = np.reshape(
        data, (cb_data.attr.height * 3 // 2, cb_data.attr.width)
    ).astype(np.uint8)
    if cb_data.frame.eos:
        cb_data.attr.video_frame.put([yuvImg, cb_data.frame.eos])
    else:
        cb_data.attr.video_frame.queue.clear()
        cb_data.attr.video_frame.put([yuvImg, cb_data.frame.eos])
    cb_data.frame_pool.push(cb_data.frame.data)
    return 0


def save_file_cb(cb_data: save_file_cb_data):
    packet = cb_data.packet
    if packet.eos:
        cb_data.recv_pool.push(packet.data)
        return 0

    data_size, ret = sdk.lyn_enc_get_remote_packet_valid_size(packet)
    common.error_check(ret, "lyn_enc_get_remote_packet_valid_size")
    data = np.zeros(data_size, dtype=np.byte)
    data_ptr = sdk.lyn_numpy_to_ptr(data)
    ret = sdk.lyn_memcpy(
        data_ptr, packet.data, data_size, sdk.lyn_memcpy_dir_t.ServerToClient
    )
    common.error_check(ret, "lyn_memcpy")
    cb_data.recv_pool.push(packet.data)
    with open(cb_data.output_path, "ab") as f:
        f.seek(0, os.SEEK_END)
        file_size = f.tell()
        if file_size < 500 * 1024 * 1024:
            f.write(data.tobytes())
        else:
            print(
                "only support 500M.", cb_data.output_path, "now file size:", file_size
            )
    return ret


def get_current_process_name():
    script_path = sys.argv[0]
    filename = os.path.basename(script_path)
    filename_without_extension = os.path.splitext(filename)[0]
    return filename_without_extension


def window_exists(window_name):
    prop = cv2.getWindowProperty(window_name, cv2.WND_PROP_VISIBLE)
    return prop >= 0


# 为每个通道画边界
def draw_line_grid(x_index, y_index, cell_width, cell_height, grid) -> None:
    x_start = x_index * cell_width
    x_end = (x_index + 1) * cell_width
    y_start = y_index * cell_height
    y_end = (y_index + 1) * cell_height
    color = (255, 255, 0)
    cv2.rectangle(grid, (x_start, y_start), (x_end, y_end), color, 6)


# 将每个通道显示在窗口的对应位置
def draw_frame(x_index, y_index, cell_width, cell_height, frame, grid) -> None:
    x_start = x_index * cell_width
    x_end = (x_index + 1) * cell_width
    y_start = y_index * cell_height
    y_end = (y_index + 1) * cell_height

    # Copy the corresponding portion of the input image to the grid
    try:
        grid[y_start:y_end, x_start:x_end] = frame
    except:
        print(f"grid shape: { grid.shape }")
        print(f"x_start: {x_start}")
        print(f"x_end: {x_end}")
        print(f"y_start: {y_start}")
        print(f"y_end: {y_end}")


# 根据总通道个数，确定窗口的行列数
def get_row_col_num(total, cell_width, cell_height) -> Tuple[int, int]:
    col_num = math.floor(math.sqrt(total))
    row_num = math.ceil(math.sqrt(total))
    if row_num * col_num < total:
        col_num = row_num
    if cell_width * col_num < cell_height * row_num:
        row_num, col_num = col_num, row_num
    return row_num, col_num


# 根据通道的index，获取对应的列的index
def get_col_index(index, col_num):
    return index % col_num


# 根据通道的index，获取对应的行的index
def get_row_index(index, col_num):
    return index // col_num


# 显示opencv窗口
def show_window_grid(window_name):
    global grid
    if grid is None:
        print("grid is None")
        return
    cv2.imshow(window_name, grid)
    cv2.waitKey(2)


def resize_nv12_image(nv12_array, width, height, new_width, new_height):
    if width == height and new_width == new_height:
        nv12_resized = cv2.cvtColor(nv12_resized, cv2.COLOR_YUV2BGR_NV12)
        return nv12_resized
    nv12_array = nv12_array.flatten()
    y_size = width * height

    y = nv12_array[:y_size].reshape((height, width))
    uv = nv12_array[y_size:].reshape((height // 2, width // 2, 2))

    # Resize the Y component
    y_resized = cv2.resize(y, (new_width, new_height))

    # Resize the UV component
    uv_resized = cv2.resize(uv, (new_width // 2, new_height // 2))

    # Concatenate the resized Y and UV components
    nv12_resized = np.concatenate((y_resized.flatten(), uv_resized.flatten())).reshape(
        new_height * 3 // 2, new_width
    )
    nv12_resized = cv2.cvtColor(nv12_resized, cv2.COLOR_YUV2BGR_NV12)
    return nv12_resized


# 生成要显示的窗口的内容
def draw_window_grid(index, total, frame):
    global grid, cell_width, cell_height
    row_num, col_num = get_row_col_num(total, cell_width, cell_height)
    resized_array = resize_nv12_image(
        frame, frame.shape[1], frame.shape[0] * 2 // 3, cell_width, cell_height
    )

    draw_frame(
        get_col_index(index, col_num),
        get_row_index(index, col_num),
        cell_width,
        cell_height,
        resized_array,
        grid,
    )
    draw_line_grid(
        get_col_index(index, col_num),
        get_row_index(index, col_num),
        cell_width,
        cell_height,
        grid,
    )


# 从帧队列中取出元素，并处理和显示
def show_grid_video(video_frames, cancel_flag):
    # show window
    while True:
        for i in range(len(video_frames)):
            try:
                frame = video_frames[i].get(False)
            except:
                if cancel_flag.value:
                    os._exit(0)
                else:
                    continue
            # todo: 使用在线流常收到eos，导致早早退出
            if frame[1] or cancel_flag.value:
                cv2.destroyAllWindows()
                os._exit(0)
            draw_window_grid(i, len(video_frames), frame[0])

        show_window_grid(get_current_process_name())


def start_process(target, channel_infos, cancel_flag):
    channel_threads = []
    video_frames = []
    for channel_info in channel_infos:
        video_frame = queue.Queue(10)
        video_frames.append(video_frame)
        channel_thread = threading.Thread(
            target=target, args=(channel_info, video_frame)
        )
        channel_thread.start()
        channel_threads.append(channel_thread)
    if channel_infos[0].show_type == 0:
        global grid, cell_width, cell_height
        if len(channel_infos) == 1:
            cell_width, cell_height = 1280, 720
        elif len(channel_infos) <= 9:
            cell_width, cell_height = 640, 360
        else:
            cell_width, cell_height = 480, 270
        channels = 3
        row_num, col_num = get_row_col_num(len(channel_infos), cell_width, cell_height)
        cv2.namedWindow(get_current_process_name(), cv2.WINDOW_NORMAL)
        cv2.resizeWindow(
            get_current_process_name(), col_num * cell_width, row_num * cell_height
        )
        grid = np.zeros(
            (row_num * cell_height, col_num * cell_width, channels), dtype=np.uint8
        )
        show_grid_video(video_frames, cancel_flag)
    else:
        for channel_thread in channel_threads:
            channel_thread.join()

def get_channel_infos(args):
    channel_infos = []
    device_ids = [int(id) for id in args.device_id.split(",")]

    if args.config_file is not None:
        with open(args.config_file, "r") as json_file:
            config_data = json.load(json_file)
        config_list = config_data.get("config", [])
        for config_item in config_list:
            device_id = config_item.get("devicesID", None)
            channels = config_item.get("channels", [])
            channel_idx = 0
            for channel in channels:
                channel_num = channel.get("channel", 1)
                for i in range(channel_num):
                    channel_info = copy.deepcopy(args)
                    channel_info.input_path = channel.get("input", None)
                    channel_info.output_path = channel.get("output", None)
                    channel_info.channel_id = channel_idx
                    channel_idx += 1
                    channel_info.device_id = device_id
                    channel_info.model_path = channel.get("modelPath", None)
                    channel_info.another_model_path = channel.get(
                        "anotherModelPath", None
                    )
                    channel_info.plugin_path = channel.get("pluginPath", None)
                    channel_info.osd_plugin = channel.get("osdPlugin", None)
                    channel_info.post_plugin = channel.get("postPlugin", None)
                    channel_info.label_file = channel.get("labelFile", None)
                    channel_info.show_type = config_data.get("showType", None)
                    channel_infos.append(channel_info)
    else:
        # 若不通过json配置，则channel_info相当于args
        for device_id in device_ids:
            for channel in range(args.channel_num):
                channel_info = copy.deepcopy(args)
                channel_info.device_id = device_id
                channel_info.channel_id = channel
                channel_infos.append(channel_info)
    return channel_infos
